feat(http): add stateless flag to mount_http()#290
Open
easydaniel wants to merge 1 commit intotadata-org:mainfrom
Open
feat(http): add stateless flag to mount_http()#290easydaniel wants to merge 1 commit intotadata-org:mainfrom
easydaniel wants to merge 1 commit intotadata-org:mainfrom
Conversation
mount_http() currently hardcodes StreamableHTTPSessionManager(stateless=False). Stateful sessions are held in one process's memory, which makes them unusable in multi-replica deployments behind a load balancer: without sticky-session routing on the Mcp-Session-Id header, a client's follow-up request lands on a different replica and fails with "Session not found". The MCP spec allows stateless streamable HTTP (see StreamableHTTPSessionManager's `stateless` param in the python-sdk), and it's the right mode for request/response-only MCP servers — a FastAPI app exposing REST handlers as tools doesn't need progress notifications, resumability, or server-initiated pushes. Add an opt-in `stateless: bool = False` parameter to `mount_http()`, threaded through `FastApiHttpSessionManager` to the underlying `StreamableHTTPSessionManager`. The default is unchanged, so existing users are unaffected. Tests: - tests/test_http_real_transport_stateless.py mirrors the existing real-transport tests with `mount_http(stateless=True)` and asserts: - initialize response does NOT return an mcp-session-id header - tools/list succeeds without a prior initialize or session header - tools/call succeeds without a prior initialize or session header This unblocks stateless REST-gateway style MCP servers running multiple replicas.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Add an opt-in
stateless: bool = Falseparameter toFastApiMCP.mount_http(), threaded throughFastApiHttpSessionManagerto the underlyingStreamableHTTPSessionManager. Default behavior is unchanged.Motivation
mount_http()currently hardcodesStreamableHTTPSessionManager(stateless=False)(fastapi_mcp/transport/http.py:56). Stateful sessions live in one process's memory dict keyed onMcp-Session-Id, which makes them unusable in multi-replica deployments behind a load balancer:initialize→ lands on replica A → A creates a session, returnsMcp-Session-Id: abc.tools/listwith that session header → lands on replica B.abc→ returns400 Bad Request: Missing session ID(or404 Session not found).Workarounds like sticky-session routing on
Mcp-Session-Idat the load balancer layer are fragile and not something we'd like to require from every deployment. Worse, because MCP clients rarely send theDELETEthat evicts a session,_server_instancesgrows unbounded — eachinitializeleaks one transport until the process restarts.The MCP spec explicitly permits stateless streamable HTTP (see the
statelessdocstring onStreamableHTTPSessionManager), and it's the right mode for request/response-only MCP servers: a FastAPI app exposing REST handlers as tools doesn't need progress notifications, resumability, or server-initiated pushes.Changes
fastapi_mcp/server.py— add astateless: bool = Falsekwarg toFastApiMCP.mount_http()with a docstring explaining the trade-off; pass it through toFastApiHttpSessionManager.fastapi_mcp/transport/http.py—FastApiHttpSessionManager.__init__gainsstateless: bool = False, stored asself.stateless. The hardcodedstateless=Falsein_ensure_session_manager_startedbecomesstateless=self.stateless, andevent_storeis forced toNonein stateless mode (the SDK doesn't track events without sessions). Updated the inline comment to reflect that stateful/stateless is now a user choice.tests/test_http_real_transport_stateless.py(new) — mirrors the existingtest_http_real_transport.pyreal-uvicorn fixture pattern, mounts withstateless=True, and asserts:test_stateless_initialize_has_no_session_header— initialize must NOT emitmcp-session-id.test_stateless_tools_list_without_session—tools/listsucceeds without prior initialize and without any session header.test_stateless_call_tool_without_session—tools/callsucceeds without prior initialize and without any session header.Test Plan
ruff check .passes.mypy fastapi_mcp/transport/http.py fastapi_mcp/server.py— no issues in the touched files (pre-existing error infastapi_mcp/types.py:135is unrelated — pydantic API drift).pytest tests/test_http_real_transport_stateless.py tests/test_http_real_transport.py— new stateless tests pass and existing stateful tests still pass, confirming backwards compatibility.statelessarg) unchanged — existing test suite untouched.Backwards Compatibility
None broken.
statelessdefaults toFalse, which preserves the 0.4.0 behavior exactly. Existing users opt in by passingmount_http(stateless=True).Use Case
We're running a stateless REST-gateway style MCP server (
FastApiMCPover our external API gateway) as a multi-replica deployment behind Kong. Every tool is a 1:1 wrapper over a FastAPI handler with a single JSON-RPC request/response — no progress, no streams, no resumability. Without this flag we have to reimplementmount_httpin our own repo to callStreamableHTTPSessionManagerdirectly; with it we get a one-line upgrade back to the supported public API.Happy to adjust the docstring wording, test layout, or anything else — thanks for the library!